/**
 * File: StartBatchProcessing.java
 * Created by: mhaimel
 * Created on: 16 Jul 2009
 * CVS:  $Id: AbstractPipelineBootstrapper.java,v 1.3 2009/12/07 16:35:38 mhaimel Exp $
 */
package uk.ac.ebi.curtain;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.ac.ebi.curtain.configuration.CurtainConfig;
import uk.ac.ebi.curtain.configuration.CurtainContextModule;
import uk.ac.ebi.curtain.configuration.CurtainInputArguments;
import uk.ac.ebi.curtain.configuration.DefaultModule;
import uk.ac.ebi.curtain.management.CurtainControllerFactory;
import uk.ac.ebi.curtain.management.TypeInfo;
import uk.ac.ebi.curtain.pipeline.CurtainPipelineManager;
import uk.ac.ebi.curtain.service.IdentifierFilepositionIndex;
import uk.ac.ebi.curtain.util.FileIndexReader;
import uk.ac.ebi.curtain.util.FileIndexWriter;
import uk.ac.ebi.curtain.util.FileIndexUtil.FileIndexEvent;
import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.model.Strand;

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

/**
 * @author mhaimel
 *
 */
public abstract class AbstractPipelineBootstrapper {
	private Log log= null;
	
	private CurtainConfig config;
	private CurtainContext context;
	private CurtainPipelineManager manager;
	
	public AbstractPipelineBootstrapper() {
		// do nothing;
	}
	
	protected abstract CurtainInputArguments parseArguments(String[] args);
	
	public String printHelp(){
		Injector inj = getInjector(new CurtainContextImpl(-1));
		// CurtainControllerFactory
		CurtainControllerFactory ctr = inj.getInstance(CurtainControllerFactory.class);
		List<TypeInfo> types = ctr.getTypes();
		StringBuilder sb = new StringBuilder();
		sb.append("Curtain pipelines:\n-----------------------------------\n");
		for(TypeInfo ty : types){
			if(ty.getMain()){
				sb.append("    ")
					.append(ty.getName())
					.append("\t  :  ")
					.append(ty.getDescription())
					.append("\n");
			}
		}
		return sb.toString();
	}
	
	public void boot(String[] args){
		CurtainInputArguments arguments = parseArguments(args);
		CurtainContextImpl impl = new CurtainContextImpl(arguments.getCurtainIteration());
		impl.setArguments(arguments);
		Injector injector = getInjector(impl);
		impl.setInjector(injector);
		injector.injectMembers(this);
		this.run();
	}

	private Injector getInjector(CurtainContextImpl impl) {
		return Guice.createInjector(
				new CurtainContextModule(impl),
				new DefaultModule(impl));
	}
	
	protected void run() {
		try{
			String contr = getControllerType();
			getLog().info("Check and load configuration for " + contr + " ... ");
			_clearConfiguration();
			_checkWorkingDir();
			_checkFilePositionIndex();
			registerSequenceFiles();

			getLog().info("Connect pipeline " + contr + " ... ");
			connectPipeline(contr);
			
			getLog().info("Boot pipeline " + contr + " ... ");
			bootPipeline(contr);

			getLog().info("Process pipeline " + contr + " ... ");
			processPipeline(contr);

			getLog().info("Wait pipeline to finish " + contr + " ... ");
			getManager().shutdownAndWait(false);
			List<Object> resSet = getManager().getResult(contr);
			getLog().info("Finished Pipeline with " + resSet.size() + " results");
		} catch (Exception e) {
			getManager().shutdown(true);
			getLog().error("Problems with the pipeline", e);
			exit(99);
		}
	}

	protected void bootPipeline(String contr) {
		getManager().startPipeline(contr);
	}

	protected void connectPipeline(String contr) {
		getManager().connectPipeline(contr);
	}
	
	protected void processPipeline(String type) {
		getManager().handle(type, getContext().currentFiles().getBucketDir());
	}

	protected abstract String getControllerType();

	protected void _checkWorkingDir() {
		{
			File dir = getContext().currentFiles().getWorkingDir();
			if(!dir.exists() && !dir.mkdirs()){
				throw new CurtainUncheckedException("Not able to create working directory " + dir);
			}
		}
//		{
//			File dir = getContext().currentFiles().getContigDir();
//			if(!dir.exists() && !dir.mkdir()){
//				throw new CurtainUncheckedException("Not able to create working directory " + dir);				
//			}
//		}
	}
	
	protected void _clearConfiguration() {
		if(deleteWorkingDirecotry()){
			for(ReadType rt : ReadType.values()){
				deleteFile(getContext().currentFiles().getEntryFilePosition(rt));
				deleteFile(getContext().currentFiles().getEntryFilePosition(rt));		
			}	
			deleteDir(getContext().currentFiles().getWorkingDir());
//			deleteFile(getContext().currentFiles().getConfigurationInformation());
//			deleteFile(getContext().currentFiles().getEntryFilePosition());		
//			deleteFile(getContext().currentFiles().getUnmappedFilePosition());	
//			deleteDir(getContext().currentFiles().getContigDir());
		}
	}
	
	protected abstract boolean deleteWorkingDirecotry();


	protected Log getLog() {
		if(null == log){
			log = LogFactory.getLog(this.getClass());
		}
		return log;
	}
	
	protected void deleteFile(File file){
		if(file.exists()){
			getLog().info("Delete file " + file);
			if(!file.delete()){
				throw new CurtainUncheckedException("Failed deleting File " + file);
			}
		}
	}
	
	protected void deleteDir(File dir){
		if(dir.exists()){
			if(dir.isDirectory()){
				getLog().info("Delete directory " + dir);
				try {
					FileUtils.deleteDirectory(dir);
				} catch (IOException e) {
					throw new CurtainUncheckedException("Failed deleting File " + dir,e);
				}
			} else {
				throw new CurtainUncheckedException("Expected directory: " + dir);
			}
		}
	}
	
	protected void _checkFilePositionIndex() {
		File cFile = getContext().currentFiles().getConfigurationInformation();
		IdentifierFilepositionIndex idx = getContext().getPositionIndex(cFile);
		if(cFile.exists()){
			idx.synchronize();
		}
		if(!idx.isWriteable()){
			idx.setWriteOnly();
		}
		FileIndexWriter out = null;
		try{
			out = idx.getWriter();
			for(FileInfo f : getContext().getArguments().getFiles(FileType.values())){
				Integer cat = getContext().getArguments().getCategory(f);
				out.addFile(f, cat);
				Integer insLen = getContext().getArguments().getInsertLength(f);
				Integer insSd = getContext().getArguments().getInsertLengthSd(f);
				if(null != insLen){
					out.addInsertLength(cat, insLen);
				}
				if(null != insSd){
					out.addInsertLengthSd(cat, insSd);
				}
			}
		} finally{
			FileIO.closeQuietly(out);
			FileIO.closeQuietly(idx);
		}
	}
	

	protected void registerSequenceFiles() {
		File cFile = getContext().currentFiles().getConfigurationInformation();
		IdentifierFilepositionIndex idx = getContext().getPositionIndex(cFile);
		idx.setReadOnly();
		FileIndexReader reader = null;
		try{
			reader = idx.getReader();
			reader.setEventHandler(new FileIndexEvent(){

				public void addContig(FileInfo fi, String key, Long pos) {/* empty*/}

				public void addContigPosition(FileInfo fi, Long pos) {/* empty*/}

				public void addFile(FileInfo fi, Integer category) {
					getContext().getArguments().addFile2Category(category,fi);
				}

				public void addInsertLength(Integer category, Integer insLen) {
					getContext().getArguments().addInsertLength2Category(category, insLen);
				}

				public void addInsertLengthSd(Integer category, Integer sd) {
					getContext().getArguments().addInsertLengthSd2Category(category, sd);
				}

				public void addRead(FileInfo fi, String key, Strand strand,Long pos) {/* empty*/}

				public void addReadPosition(FileInfo fi, Strand strand, Long pos) {/* empty*/}

				public void close() throws IOException {/* empty*/}

				public void addFileId(Integer fid, FileInfo fi) {
					getContext().getGlobalFileIndex().register(fid, fi);
				}

				@Override
				public void addCategory(Integer category, String id) {
					getContext().getArguments().addCategory(category,id);
				}});
			reader.readAll();
		} catch (IOException e) {
			throw new CurtainUncheckedException("Problems reading initialization file!",e);
		}finally{
			FileIO.closeQuietly(reader);
		}
	}
	
	protected void exit(Integer id){
		System.exit(id);
	}
	
	protected CurtainConfig getConfig() {
		return config;
	}

	@Inject
	public void setConfig(CurtainConfig config) {
		this.config = config;
	}
	
	protected CurtainPipelineManager getManager() {
		return manager;
	}
	
	@Inject
	public void setManager(CurtainPipelineManager manager) {
		this.manager = manager;
	}
	
	@Inject
	public void setContext(CurtainContext context) {
		this.context = context;
	}

	public CurtainContext getContext() {
		return context;
	}

}
